package cwcFramework;

import java.io.IOException;

import org.waveprotocol.wave.model.id.*;

import java.util.*;
import java.util.logging.Logger;

import com.google.wave.api.*;
import com.google.wave.api.JsonRpcConstant.ParamsProperty;

/**
 * This is a framework for cross wave communication in Google Wave. This framework allows you to 
 * create extensions in Google Wave which can communicate from one wave to another using the
 * Robot Active API v2.0 of Google Wave in an effortless and robust way.
 * <br><br>
 * A robot implementing this framework is able to setup private waves for different users and
 * communicate between these private waves with the use of the provided methods in the framework.
 * This makes it easier to develop and deploy a robot, by hiding all the cumbersome method calls 
 * involved in cross wave communication in Google Wave and also providing error handling, which makes
 * debugging of robots much less complicated.
 * <br><br>
 * To use this Framework please add this JAR library to your project and implement a robot class 
 * extending this abstract class.
 * <br><br>
 * You should also add the following libraries to your project:
 * <UL>
 * <LI>wave-model
 * <LI>wave-robot
 * <LI>oauth
 * <LI>gson
 * <LI>common-codecs
 * </UL>
 * You can find all these libraries at the <a href="http://code.google.com/p/wave-robot-java-client/">
 * Wave Robot Java API site</a>.
 * <br><br>
 * Next, you should register your robot with the RPC Server to be able to connect to it.
 * Use the following sites to register your robot.<br>
 * For Wave sandbox: <a href="http://wave.google.com/a/wavesandbox.com/robot/register">
 * http://wave.google.com/a/wavesandbox.com/robot/register</a><br>
 * For Wave preview: <a href="http://wave.google.com/wave/robot/register">
 * http://wave.google.com/wave/robot/register</a><br>
 * Override and implement the methods getVerificationToken, getSecurityToken,
 * getOAuthKey and getOAuthSecret in your robot according to the registering site.
 * <br><br>
 * Finally implement the methods of events which should call startNAryCommunication or 
 * startBinaryCommunication to start the communication and override the method 
 * initializePrivateWaves to customize the private waves of the participants.
 * <br><br><br>
 * DISCLAIMER:
 * In no way shall the authors of this framework (Romain Beker, Conrado Plano, Pierre-Olivier Trabichet)
 * be responsible to anyone or anything for any data, personal or other losses, physical, psychological 
 * or other damages or negative results or consequences, direct or indirect, caused by using the Cross Wave
 * Communication Framework or in any other way by the framework itself.
 * <br><br>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *	<br><br>
 *      http://www.apache.org/licenses/LICENSE-2.0
 *	<br><br>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * @author Romain Beker, Conrado Plano and Pierre-Olivier Trabichet
 * @version 1.04
 */
@SuppressWarnings("serial")
public abstract class CWCFAbstractRobot extends AbstractRobot {

	private static final String RPC_SERVER_URL_WS = "http://sandbox.gmodules.com/api/rpc";
	private static final String DOMAIN_WS = "wavesandbox.com";
	private static final String RPC_SERVER_URL_WP = "http://gmodules.com/api/rpc";
	private static final String DOMAIN_WP = "googlewave.com";
	
	protected static final Logger LOG = Logger.getLogger(CWCFAbstractRobot.class.getName());
	
	/**
	 * Abstract constructor for the robot. Call "super()" from the robot constructor.
	 */
	public CWCFAbstractRobot() {
		setupVerificationToken(getVerificationToken(), getSecurityToken());
		if (getOAuthKey(true) != null & getOAuthSecret(true) != null)
			setupOAuth(getOAuthKey(true), getOAuthSecret(true), RPC_SERVER_URL_WS);  
		if (getOAuthKey(false) != null & getOAuthSecret(false) != null)
			setupOAuth(getOAuthKey(false), getOAuthSecret(false), RPC_SERVER_URL_WP);
		setAllowUnsignedRequests(true);
	}
	
	/**
	 * This function should be overridden in the robot implementing the CWCFramework.
	 * Returns the EMail address of the robot in the format "robot@appspot.com".
	 * 
	 * @return EMail address of the robot in the format "robot@appspot.com"
	 */	
	protected abstract String getRobotEMail();
	
	/**
	 * This function should be overridden in the robot implementing the CWCFramework.
	 * Returns the verification token obtained from the registration site.
	 * 
	 * @return Verification Token
	 */	
	protected abstract String getVerificationToken();

	/**
	 * This function should be overridden in the robot implementing the CWCFramework.
	 * Returns the security token obtained from the registration site.
	 * 
	 * @return Security Token
	 */	
	protected abstract String getSecurityToken();
	
	/**
	 * This function should be overridden in the robot implementing the CWCFramework.
	 * Returns the authorization key obtained from the registration site after having verified your robot.
	 * If usingWaveSandbox is true it returns the key for the Wave Sandbox server,
	 * otherwise the key for the Wave Preview server.
	 * 
	 * @param  usingWaveSandbox  Is the key for the Wave Sandbox server? If False, then it is for the Preview server
	 * @return Authorization Key
	 */	
	protected abstract String getOAuthKey(boolean usingWaveSandbox);
	
	/**
	 * This function should be overridden in the robot implementing the CWCFramework.
	 * Returns the authorization secret obtained from the registration site after having verified your robot.
	 * If usingWaveSandbox is true it returns the secret for the Wave Sandbox server,
	 * otherwise the secret for the Wave Preview server.
	 * 
	 * @param  usingWaveSandbox  Is the secret for the Wave Sandbox server? If False, then it is for the Preview server
	 * @return Authorization Secret
	 */	
	protected abstract String getOAuthSecret(boolean usingWaveSandbox);

	/**
	 * Starts the N-ary communication.
	 * Calls createPrivateWaves with all participants but robot
	 * 
	 * @param  participants  All participants
	 * @param  bufferWave The wavelet in which the robot resides.
	 */
	protected void startNAryCommunication(Participants participants, Wavelet bufferWave){
		// get all participants but robot
		List<String> partList = new LinkedList<String>();
		Object[] partArray = participants.toArray();
		for (Object participant : partArray){
			if (!((String) participant).equals(getRobotEMail())){
				partList.add((String) participant);
			}
		}
		
		// check to see if there are more than 2 participants
		if (partList.size() < 2){
			String mess = "Nary communication supports 2 or more participants only";
			mess += "\nPlease add this robot to a wave with two or more participants";
			LOG.info(mess);
		}else{
			// create private waves
			createPrivateWaves(partList, bufferWave);
		}
	}
	
	/**
	 * Starts the binary communication.
	 * Checks if only two participants.
	 * Calls createPrivateWaves with all participants but robot.
	 * 
	 * @param  participants  The two participants
	 * @param  bufferWave The wavelet in which the robot resides.
	 */
	protected void startBinaryCommunication(Participants participants, Wavelet bufferWave){
		// get all participants but robot
		List<String> partList = new LinkedList<String>();
		Object[] partArray = participants.toArray();
		for (Object participant : partArray){
			if (!((String) participant).equals(getRobotEMail())){
				partList.add((String) participant);
			}
		}
		
		// check to see if there are only 2 participants
		if (partList.size() != 2){
			String mess = "Binary communication supports 2 participants only";
			mess += "\nPlease add this robot to a wave with two participants only";
			LOG.info(mess);
		}else{
			// create private waves
			createPrivateWaves(partList, bufferWave);
		}
	}
	
	/**
	 * Sets up cross-wave communication for participants.
	 * Creates a private wave for each participant.
	 * Calls initializePrivateWaves for each private wave.
	 * Submits the waves to the RPC server.
	 * 
	 * @param  partList  List of participants.
	 * @param  bufferWave The wavelet in which the robot resides.
	 */
	private void createPrivateWaves(List<String> partList, Wavelet bufferWave){
		// set list of private waves
		ArrayList<Wavelet> waveList = new ArrayList<Wavelet>();
		String rpcServerURL = "";
		if (bufferWave.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (bufferWave.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		
		//create private wave for each participant
		for (String participant : partList){
			Set<String> newPartSet = new HashSet<String>();
			newPartSet.add(participant);
			Wavelet privateWave = this.newWave(bufferWave.getDomain(), newPartSet);
			privateWave.getDataDocuments().set("[CWCFAbstractRobot:userID]",participant);
			privateWave.getRootBlip().append("\n");
			//Submit the wave to get WaveID and WaveletID from JSON Response
			WaveId remoteWaveId = null;
			WaveletId remoteWaveletId = null;
			try {
				List<JsonRpcResponse> jrrs = this.submit(privateWave, rpcServerURL); 
					for(JsonRpcResponse r : jrrs) 
					{ 
						Map<ParamsProperty, Object> data = r.getData(); 
						if(data.containsKey(ParamsProperty.WAVE_ID)) 
						{ 
							remoteWaveId = WaveId.deserialise((String) data.get(ParamsProperty.WAVE_ID));
							
						}
						if(data.containsKey(ParamsProperty.WAVELET_ID)) 
						{ 
							remoteWaveletId = WaveletId.deserialise((String) data.get(ParamsProperty.WAVELET_ID));
						}
					} 
			} catch (IOException e) {
				LOG.severe("Wave could not be submitted: " + e.getMessage());
			}
			
			//Fetch the wave again using the Wave and Wavelet ID
			privateWave = null;
			try {
				privateWave = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
			} catch (IOException e) {
				String mess = "\nError while fetching remote wave:";
				mess+="\n"+e.getMessage();
				LOG.warning(mess);
			}
			waveList.add(privateWave);
		}

		// set up communication between private waves
		// for each private wave localWave
		for (Wavelet localWave : waveList){
			String otherParticipants = "";
			// for each other private wave remoteWave
			for (Wavelet remoteWave : waveList){
				// if remoteWave is other than localWave
				if (!remoteWave.getWaveId().serialise().equalsIgnoreCase(localWave.getWaveId().serialise())){
					// get userID from data document
					String remoteParticipant = remoteWave.getDataDocuments().get("[CWCFAbstractRobot:userID]");
					// set reference to wave
					localWave.getDataDocuments().set("[CWCFAbstractRobot:waveId]" + remoteParticipant, remoteWave.getWaveId().serialise());
					localWave.getDataDocuments().set("[CWCFAbstractRobot:waveletId]" + remoteParticipant, remoteWave.getWaveletId().serialise());
					// add other participant to list of participants
					otherParticipants += remoteParticipant + ";";		
				}
			}
			// set list of other participants in localWave
			localWave.getDataDocuments().set("[CWCFAbstractRobot:participants]" , otherParticipants.substring(0,otherParticipants.length()-1));
		}
		
		//Initialize private waves
		initializePrivateWaves(bufferWave, waveList);
		
		//submit private waves
		for (Wavelet wave : waveList) {
			wave.submitWith(bufferWave);
		}
	}
	
	
	/**
	 * This function should be overridden in the robot implementing the CWCFramework.
	 * Initializes the private waves.
	 * It is used in case private waves need to be initialized with some content such as a gadget.
	 * 
	 * @param  bufferWave The wavelet in which the robot resides.
	 * @param  privateWaves The private wavelets to be initialized.
	 */
	protected abstract void initializePrivateWaves(Wavelet bufferWave, ArrayList<Wavelet> privateWaves);
	
	/**
	 * Returns a list with all other (not the owner of the private wave) participants 
	 * (String of ID - email) in the communication.
	 * 
	 * @param  localWave The local wave from which the function is called.
	 * @return the list of other participants.
	 */
	protected List<String> getAllOtherParticipants (Wavelet localWave){
		String participants = localWave.getDataDocuments().get("[CWCFAbstractRobot:participants]");
		List<String> partList = Arrays.asList(participants.split(";"));
		return partList;
	}
	
	/**
	 * Returns the wave Id of a participant.
	 * If participant is null, then it returns the wave Id of the first participant
	 * in the list of participants in localWave (binary communication).
	 * 
	 * @param  participant The participant name.
	 * @param  localWave The local wave from which the function is called.
	 * @return the wave Id.
	 */
	protected WaveId getRemoteWaveId(String participant, Wavelet localWave){
		if (participant == null){
			String participants = localWave.getDataDocuments().get("[CWCFAbstractRobot:participants]");
			List<String> partList = Arrays.asList(participants.split(";"));
			participant = partList.get(0);
		}
		String serOppWaveId = localWave.getDataDocuments().get("[CWCFAbstractRobot:waveId]" + participant);
		if (serOppWaveId.equals("")){
			LOG.severe("Participant WaveId not found: "+ participant);
			return null;
		}else{
			WaveId oppWaveId = WaveId.deserialise(localWave.getDataDocuments().get("[CWCFAbstractRobot:waveId]" + participant));
			return oppWaveId;
		}
	}
	
	/**
	 * Returns the wave Id of the other participant in a binary communication.
	 * 
	 * @param  localWave The local wave from which the function is called.
	 * @return the wave Id.
	 */
	protected WaveId getRemoteWaveId(Wavelet localWave){
		return getRemoteWaveId(null, localWave);
	}
	
	/**
	 * Returns the wavelet Id of a participant.
	 * If participant is null, then it returns the wave Id of the first participant
	 * in the list of participants in localWave (binary communication).
	 * 
	 * @param  participant The participant name.
	 * @param  localWave The local wave from which the function is called.
	 * @return the wavelet Id.
	 */
	protected WaveletId getRemoteWaveletId(String participant, Wavelet localWave){
		if (participant == null){
			String participants = localWave.getDataDocuments().get("[CWCFAbstractRobot:participants]");
			List<String> partList = Arrays.asList(participants.split(";"));
			participant = partList.get(0);
		}
		WaveletId oppWaveletId = WaveletId.deserialise(localWave.getDataDocuments().get("[CWCFAbstractRobot:waveletId]"+ participant));
		return oppWaveletId;
	}
	
	/**
	 * Returns the wavelet Id of the other participant in a binary communication.
	 * 
	 * @param  localWave The local wave from which the function is called.
	 * @return the wavelet Id.
	 */
	protected WaveletId getRemoteWaveletId(Wavelet localWave){
		return getRemoteWaveletId(null, localWave);
	}
	
	/**
	 * Reads a blip from a remote private wave.
	 * 
	 * @param  remoteWaveId The wave Id of the remote wave.
	 * @param  remoteWaveletId The wavelet Id of the remote wave.
	 * @param  blipId The blip Id to read.
	 * @return the remote Blip.
	 */
	protected Blip readRemoteBlip(WaveId remoteWaveId, WaveletId remoteWaveletId, String blipId, String domain) {
		String rpcServerURL = "";
		if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		Wavelet remoteWavelet = null;
		try {
			remoteWavelet = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while reading remote blip: - could not fetch wavelet";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}
		return remoteWavelet.getBlip(blipId);
	}
	
	/**
	 * Reads the root blip from a remote private wave.
	 * 
	 * @param  remoteWaveId The wave Id of the remote wave.
	 * @param  remoteWaveletId The wavelet Id of the remote wave.
	 * @return the remote root blip.
	 */
	protected Blip readRemoteRootBlip(WaveId remoteWaveId, WaveletId remoteWaveletId) {
		String rpcServerURL = "";
		if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		Wavelet remoteWavelet = null;
		try {
			remoteWavelet = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while reading remote root blip: - could not fetch wavelet";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}
		return remoteWavelet.getRootBlip();
	}
	
	/**
	 * Writes a blip to a remote private wave.
	 * 
	 * @param  remoteWaveId The wave Id of the remote wave.
	 * @param  remoteWaveletId The wavelet Id of the remote wave.
	 * @param  initialContent The initial content string (beginning with "\n") to write to the remote wave.
	 */
	protected void writeRemoteBlip(Wavelet localWave, WaveId remoteWaveId,WaveletId remoteWaveletId, String initialContent){
		Wavelet remoteWavelet = null;
		String rpcServerURL = "";
		if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		try {
			remoteWavelet = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while writting remote blip: - could not fetch wavelet";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}
		remoteWavelet.reply(initialContent);
		try {
			submit(remoteWavelet, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while writting remote blip: - could not submit wavelet";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}
	}
	
	/**
	 * Writes a blip to all remote private waves of the other participants in localWave.
	 * 
	 * @param  localWave The local wave with all other participants and their wave IDs.
	 * @param  initialContent The initial content string (beginning with "\n") to write to the remote wave.
	 */
	protected void WriteRemoteBlipBroadcast(Wavelet localWave, String initialContent){
		//get all participants
		List<String> otherParticipants = getAllOtherParticipants(localWave);
		
		//write remote document for each participant
		for (String participant : otherParticipants){
			writeRemoteBlip(localWave, getRemoteWaveId(participant, localWave), getRemoteWaveletId(participant, localWave), initialContent);
		}
	}
	
	/**
	 * Deletes a blip from a remote private wave.
	 * 
	 * @param  remoteWaveId The wave Id of the remote wave.
	 * @param  remoteWaveletId The wavelet Id of the remote wave.
	 * @param  blipToDelete The blip to delete from the remote wave.
	 */
	protected void deleteRemoteBlip(Wavelet localWave, WaveId remoteWaveId,WaveletId remoteWaveletId, Blip blipToDelete){
		Wavelet remoteWavelet = null;
		String rpcServerURL = "";
		if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		try {
			remoteWavelet = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while deleting remote blip: - could not fetch wavelet";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}
		remoteWavelet.delete(blipToDelete);
		remoteWavelet.submitWith(localWave);
	}
	
	/**
	 * Reads a document entry from a remote private wave.
	 * 
	 * @param  remoteWaveId The wave Id of the remote wave.
	 * @param  remoteWaveletId The wavelet Id of the remote wave.
	 * @param  aKey Key corresponding to the document's entry.
	 * @return document's value corresponding to aKey.
	 */
	protected String readRemoteDocument(WaveId remoteWaveId,WaveletId remoteWaveletId, String aKey) {
		Wavelet remoteWavelet = null;
		String rpcServerURL = "";
		if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		try {
			remoteWavelet = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while reading remote document: - could not fetch wavelet";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}
		return remoteWavelet.getDataDocuments().get(aKey);
	}
	
	/**
	 * Writes a document entry to a remote private wave.
	 * 
	 * @param  remoteWaveId The wave Id of the remote wave.
	 * @param  remoteWaveletId The wavelet Id of the remote wave.
	 * @param  aKey Key corresponding to the document's entry.
	 * @param  newValue Value to write in the remote document.
	 */
	protected void writeRemoteDocument(Wavelet localWave, WaveId remoteWaveId,WaveletId remoteWaveletId, String aKey, String newValue) {
		Wavelet remoteWavelet = null;
		String rpcServerURL = "";
		if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		try {
			remoteWavelet = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while writting remote document - could not fetch wavelet:";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}
		remoteWavelet.getDataDocuments().set(aKey, newValue);
		remoteWavelet.submitWith(localWave);
	}
	
	/**
	 * Reads a state entry from a gadget in a remote private wave.
	 * 
	 * @param  remoteWaveId The wave Id of the remote wave.
	 * @param  remoteWaveletId The wavelet Id of the remote wave.
	 * @param  gadgetURL The gadget's URL.
	 * @param  propertyName The gadget's state property to read from.
	 * @return the state value corresponding to propertyName.
	 */
	protected String readRemoteGadgetState(WaveId remoteWaveId,WaveletId remoteWaveletId, String gadgetURL, String propertyName) {
		Wavelet remoteWavelet = null;
		String rpcServerURL = "";
		if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		try {
			remoteWavelet = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while reading remote gadget state - could not fetch wavelet:";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}
		BlipContentRefs remoteGadgetRef = remoteWavelet.getRootBlip().first(ElementType.GADGET, Restriction.of(Gadget.URL, gadgetURL));
		Gadget remoteGadget = (Gadget) remoteGadgetRef.value();
		String result = remoteGadget.getProperty(propertyName);
		return result;
	}
	
	/**
	 * Reads a state entry from a gadget in the local private wave.
	 * 
	 * @param  origWave The local wavelet
	 * @param  gadgetURL The gadget's URL.
	 * @param  propertyName The gadget's state property to read from.
	 * @return the state value corresponding to propertyName.
	 */
	protected String readLocalGadgetState(Wavelet origWave, String gadgetURL, String propertyName) {
		// get local gadget
		BlipContentRefs localGadgetRef = origWave.getRootBlip().first(ElementType.GADGET, Restriction.of(Gadget.URL, gadgetURL));	
		Gadget localGadget = (Gadget) localGadgetRef.value();
		String result = localGadget.getProperty(propertyName);
		return result;
	}
	
	/**
	 * Writes a state entry to a gadget in the local private wave.
	 * 
	 * @param  origWave The local wavelet
	 * @param  gadgetURL The gadget's URL.
	 * @param  propertyName The gadget's state property to write in.
	 * @param  newValue The state value to write.
	 */
	protected void writeLocalGadgetState(Wavelet origWave, String gadgetURL, String propertyName, String newValue) {
		BlipContentRefs localGadgetRef = origWave.getRootBlip().first(ElementType.GADGET, Restriction.of(Gadget.URL, gadgetURL));	
		
		HashMap<String, String> localGadgetProperties = new HashMap<String, String>();
		localGadgetProperties.put(propertyName, newValue);
		localGadgetRef.updateElement(localGadgetProperties);
	}
	
	/**
	 * Writes a state entry to a gadget in a remote private wave.
	 * 
	 * @param  remoteWaveId The wave Id of the remote wave.
	 * @param  remoteWaveletId The wavelet Id of the remote wave.
	 * @param  gadgetURL The gadget's URL.
	 * @param  propertyName The gadget's state property to write in.
	 * @param  newValue The state value to write.
	 */
	protected void writeRemoteGadgetState(Wavelet localWave, WaveId remoteWaveId,WaveletId remoteWaveletId, String gadgetURL, String propertyName, String newValue) {
		Wavelet remoteWavelet = null;
		String rpcServerURL = "";
		if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WS)){
			rpcServerURL = RPC_SERVER_URL_WS;
		}else if (remoteWaveId.getDomain().equalsIgnoreCase(DOMAIN_WP)){
			rpcServerURL = RPC_SERVER_URL_WP;
		}
		try {
			remoteWavelet = fetchWavelet(remoteWaveId, remoteWaveletId, rpcServerURL);
		} catch (IOException e) {
			String mess = "\nError while writting remote gadget state - could not fetch wavelet:";
			mess+="\n"+e.getMessage();
			LOG.warning(mess);
		}		
		BlipContentRefs remoteGadgetRef = remoteWavelet.getRootBlip().first(ElementType.GADGET, Restriction.of(Gadget.URL, gadgetURL));
		HashMap<String, String> remoteGadgetProperties = new HashMap<String, String>();
		remoteGadgetProperties.put(propertyName, newValue);
		remoteGadgetRef.updateElement(remoteGadgetProperties);
		remoteWavelet.submitWith(localWave);
	}
	
}


